package com.hmdp.service.impl;

import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

import com.hmdp.service.ILock;

/**
 * 基于Redis的分布式锁实现（生产级可用）
 * 核心解决：互斥性、防死锁、防误删
 * 注意：此类不直接作为Spring Bean（移除@Component），通过内部工厂类动态创建实例
 */
// 关键修复：移除@Component注解，避免Spring自动装配（lockName是动态值，非容器Bean）
public class SimpleRedisLock implements ILock {
    /** 锁的Redis键前缀：lock:资源标识（如lock:order:1001） */
    private static final String KEY_PREFIX = "lock:";

    /** 线程标识前缀：解决分布式环境下不同JVM线程ID重复问题 */
    private static final String THREAD_ID_PREFIX = "thread_";

    /** 锁名称=资源标识（如"order:1001"），动态传递，确保锁粒度正确 */
    private final String lockName;

    /** Redis操作模板（Spring容器Bean，通过工厂类注入） */
    private final StringRedisTemplate stringRedisTemplate;

    /** 解锁Lua脚本：原子校验并释放锁，类加载时预加载 */
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        // 加载类路径下的unlock.lua脚本（src/main/resources/unlock.lua）
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class); // 脚本返回值：0=未删除，1=删除成功
    }

    /**
     * 构造方法：接收动态锁名称和Redis模板，不依赖Spring自动注入
     * @param lockName 资源标识（如"order:1001"）
     * @param stringRedisTemplate Redis操作模板（由工厂类传入）
     */
    public SimpleRedisLock(String lockName, StringRedisTemplate stringRedisTemplate) {
        this.lockName = lockName;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 尝试获取锁（非阻塞）
     * @param timeoutSec 锁过期时间（秒），防止死锁
     * @return true=获取成功，false=获取失败
     */
    @Override
    public boolean tryLock(long timeoutSec) {
        String threadId = THREAD_ID_PREFIX + Thread.currentThread().getId();
        String lockKey = KEY_PREFIX + lockName;

        // 原子加锁：SETNX + 过期时间（避免加锁和设过期分离）
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(lockKey, threadId, timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(success);
    }

    /**
     * 释放锁（Lua脚本原子校验+删除，防误删）
     */
    @Override
    public void unlock() {
        String lockKey = KEY_PREFIX + lockName;
        String threadId = THREAD_ID_PREFIX + Thread.currentThread().getId();

        // 执行Lua脚本：仅当前线程持有锁时才删除
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(lockKey),
                threadId
        );
    }

    /**
     * Spring工厂类（唯一Spring Bean）：动态创建SimpleRedisLock实例
     * 解决：动态传递lockName，避免自动装配String类型Bean的冲突
     */
    @Component // 仅工厂类作为Spring Bean，被容器管理
    public static class SimpleRedisLockFactory {
        /** 注入Spring容器中的StringRedisTemplate（自动配置，无需手动定义） */
        @Resource
        private StringRedisTemplate stringRedisTemplate;

        /**
         * 对外提供创建锁实例的方法
         * @param lockName 动态资源标识（如"order:1001"）
         * @return 分布式锁实例
         */
        public SimpleRedisLock createLock(String lockName) {
            // 传入动态lockName和注入的Redis模板，创建锁实例
            return new SimpleRedisLock(lockName, stringRedisTemplate);
        }
    }
}